SD Card

Contenido

Como leer una tarjeta SD usando un microcontrolador AVR.

Explicacion ChatGPT

Descripcion

Las tarjetas SD funcionan con 3.3V (Tanto para alimentación como para los voltajes lógicos), para evitar tener que usar exactamente 3.3V para el microcontrolador podemos usar un modulo especifico para leer la tarjeta SD que ya hace la conversión a 3.3V.

El siguiente circuito es para MicroSD pero tambien hay variantes para SD normales:

Este circuito usa dos ICs para realizar la conversión del voltaje, uno es un 1117-3.3 que convierte voltajes maximos de 5V a voltajes de 3.3V para la alimentación.

El otro IC es un SN74HC125 que actua como buffer para las señales lógicas, pero en este caso se usa para hacer el cambio de 5V a 3.3V de las señales de comunicación entre la SD y el microcontrolador.

Ademas este circuito ya expone los pines de la tarjeta MicroSD que necesitamos: VCC, GND, MOSI, MISO, SCK y CS

La disposición de pines en tarjetas SD y MicroSD es el siguiente:

NOTA: VSS es GND.

Circuito

El circuito para conectar el lector de tarjetas y los leds para visualizar el byte es el siguiente:

Escribir tarjeta SD

En nuestro caso no estamos usando ningun sistema de archivos para almacenar información en la tarjeta SD (Aunque existen alternativas para manejar un sistema FAT con un microcontrolador AVR), así que en nuestro caso escribiremos los dato en crudo (RAW), en la tarjeta.

Para escribir (o leer) datos en la tarjeta usaremos dd, para escribir los datos de un archivo usaremos el comando asi:

dd if=hola.wav of=/dev/sdc

Podemos usar dmesg para saber cual es la ruta de dispositivo de nuestra tarjeta SD (si la acabamos de conectar el dispositivo saldrá justo en los mensajes finales de dmesg).

Si queremos leer el contenido de la SD lo hacemos así:

dd if=/dev/sdc of=miSD.dat

Una vez generado el archivo miSD.dat podemos leerlo con un editor hexadecimal, como okteta, para leer byte por byte el contenido.

Limitaciones

La lectura de la tarjeta SD es lenta, usando el SPI a máxima velocidad el tiempo de lectura es de 1 ms.

Si vamos a almacenar datos que no sean sensibles a la velocidad no tendremos problema y el uso de la SD es una buena solución.

Pero si vamos a querer leer datos de audio por ejemplo tendremos esa limitación de 1ms en la lectura, por lo tanto el audio se escuchará con microcortes cada vez que necesitemos leer la tarjeta SD para obtener nuevos samples.

EG: con un microcontrolador Atmega168pa a 8Mhz, con un audio con 8.000 samples por segundo, nos deja que cada sample tarda 125 microsegundos, y cada lectura a la tarjeta SD leemos un bloque de 512 bytes (512 samples), por lo tanto esto nos deja que cada 64 milisegundos tenemos que hacer una lectura a la tarjeta SD y esperar 1 milisegundo a que se complete la lectura, durante este tiempo estamos perdiendo samples de reproducción (8 samples), esto hace que cada 64 milisegundos haya pequeñas distorsiones en el audio. El audio es perfectamente audible y reconocible (incluso probandolo con canciones), pero ese pequeño retraso de 1 ms hace que la calidad general baje, es algo usable, pero no para una calidad 100% nitida.

Codigo

Ejemplo básico para leer el primer bloque de una tarjeta SD (512 bytes) y hacer que el primer byte se ponga en PORTD para visualizarlo a traves de LEDs.

#include <avr/io.h>
#include <util/delay.h>
#include <avr/power.h>
#include <stdint.h>

// =======================
// SPI
// =======================
void spi_init() {
    DDRB |= (1<<PB3) | (1<<PB5) | (1<<PB2); // MOSI, SCK, SS
    DDRB &= ~(1<<PB4); // MISO

    SPCR = (1<<SPE) | (1<<MSTR) | (1<<SPR1) | (1<<SPR0); // clk = F/128
}

uint8_t spi_transfer(uint8_t data) {
    SPDR = data;
    while (!(SPSR & (1<<SPIF)));
    return SPDR;
}

void spi_set_fast() {
    // SPI = F_CPU / 2  (SPI2X = 1)
    SPSR |= (1<<SPI2X);
    SPCR &= ~((1<<SPR1) | (1<<SPR0));  // SPR1=SPR0=0 → F/4
    // Con SPI2X esto da F/2 → 8 MHz si tu AVR va a 16 MHz
}

// =======================
// SD Card (modo SPI)
// =======================
uint8_t sd_cmd(uint8_t cmd, uint32_t arg, uint8_t crc) {
    spi_transfer(0x40 | cmd);
    spi_transfer(arg >> 24);
    spi_transfer(arg >> 16);
    spi_transfer(arg >> 8);
    spi_transfer(arg);
    spi_transfer(crc);

    uint8_t r;
    for (uint8_t i = 0; i < 10; i++) {
        r = spi_transfer(0xFF);
        if (!(r & 0x80)) break;
    }
    return r;
}

void sd_init() {
    // SD requiere >74 pulsos de reloj con CS alto
    PORTB |= (1<<PB2);
    for (int i=0; i<10; i++) spi_transfer(0xFF);

    // CMD0
    uint8_t r;
    do {
        PORTB &= ~(1<<PB2);
        r = sd_cmd(0, 0, 0x95);
        PORTB |= (1<<PB2);
    } while (r != 0x01);

    // CMD8
    PORTB &= ~(1<<PB2);
    sd_cmd(8, 0x000001AA, 0x87);
    spi_transfer(0xFF);
    spi_transfer(0xFF);
    spi_transfer(0xFF);
    spi_transfer(0xFF);
    PORTB |= (1<<PB2);

    // ACMD41
    do {
        PORTB &= ~(1<<PB2);
        sd_cmd(55, 0, 0x65);
        r = sd_cmd(41, 0x40000000, 0x77);
        PORTB |= (1<<PB2);
    } while (r != 0x00);

    // CMD58 (descartar OCR)
    PORTB &= ~(1<<PB2);
    sd_cmd(58, 0, 0xFF);
    spi_transfer(0xFF);
    spi_transfer(0xFF);
    spi_transfer(0xFF);
    spi_transfer(0xFF);
    PORTB |= (1<<PB2);
}

uint8_t sd_read_sector(uint32_t lba, uint8_t *buf) {
    uint8_t r;

    PORTB &= ~(1<<PB2);
    r = sd_cmd(17, lba, 0xFF);
    if (r != 0x00) { PORTB |= (1<<PB2); return 1; }

    uint8_t token;
    do {
        token = spi_transfer(0xFF);
    } while (token == 0xFF);

    if (token != 0xFE) { PORTB |= (1<<PB2); return 2; }

    for (int i=0; i<512; i++)
        buf[i] = spi_transfer(0xFF);

    spi_transfer(0xFF);
    spi_transfer(0xFF);

    PORTB |= (1<<PB2);

    return 0;
}

// =======================
// Main
// =======================
int main() {

    clock_prescale_set(clock_div_1); //CPU clock 8 MHz

    uint8_t sector[512];

    DDRD = 0xFF; // LEDs en PORTD
    PORTD = 0xFF;
    spi_init();
    spi_set_fast();

    _delay_ms(50);

    sd_init();

    PORTD = 0x00;

    uint8_t err;

    while (1){
        err = sd_read_sector(0, sector);

        if (err == 0) {
            PORTD = sector[0];
        } else {
            PORTD = 0xFF; // señal de error
        }

    }
}
Tags

AVR | Diodo | SD Card